/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.path;

import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDAggregate;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDDescent;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDElement;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDFilter;
import edu.ucsd.db.bassarisk.schema.AttributeClass;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.RunClass;
import edu.ucsd.db.bassarisk.schema.Schema.ValueType;
import edu.ucsd.db.bassarisk.treepattern.DescentAxis;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public abstract class DSDBuilder<T extends DSDBuilder<T>> implements
    DSDEdgeBuilder<T>, DSDNodeBuilder<T>, DSDFilterBuilder<T> {
  private DescentAxis axis;
  private EdgeFunction func;
  private DSDElement head;
  private boolean isAggregate;
  private DSDElement tail;
  protected final ValueType type;

  protected DSDBuilder(final ValueType type) {
    this.type = type;
    this.head = null;
    this.tail = null;
  }

  protected void appendElement(final DSDElement newElem) {
    if (this.head == null) {
      this.head = newElem;
      this.tail = newElem;
    } else {
      this.tail.setNext(newElem);
      this.tail = newElem;
    }
  }

  @Override
  public DSDEdgeBuilder<T> attribute(final String name) {
    if (!this.isAggregate) {
      this.appendElement(new DSDDescent(this.axis, new AttributeClass(name),
                                        this.func == EdgeFunction.NonEmpty));
    } else {
      this.appendElement(new DSDAggregate(this.axis, new AttributeClass(name),
                                          this.func));
    }
    this.axis = null;
    this.func = null;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> child() {
    this.axis = DescentAxis.Child;
    this.func = EdgeFunction.Values;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> childAggregate(final EdgeFunction f) {
    this.axis = DescentAxis.Child;
    this.func = f;
    this.isAggregate = true;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> childFilter() {
    this.axis = DescentAxis.Child;
    this.func = EdgeFunction.NonEmpty;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> descendant() {
    this.axis = DescentAxis.Descendant;
    this.func = EdgeFunction.Values;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> descendantAggregate(final EdgeFunction f) {
    this.axis = DescentAxis.Descendant;
    this.func = f;
    this.isAggregate = true;
    return this;
  }

  @Override
  public DSDNodeBuilder<T> descendantFilter() {
    this.axis = DescentAxis.Descendant;
    this.func = EdgeFunction.NonEmpty;
    return this;
  }

  @Override
  public DSDEdgeBuilder<T> filter(final String attributeName,
                                  final String filterValue) {
    this.appendElement(new DSDFilter(new AttributeClass(attributeName), filterValue));
    return this;
  }

  @Override
  @SuppressWarnings("unchecked")
  public T finish() {
    return (T) this;
  }

  public DSDElement getHead() {
    return this.head;
  }

  public ValueType getType() {
    return this.type;
  }

  @Override
  public DSDFilterBuilder<T> positions() {
    if (!this.isAggregate) {
      this.appendElement(new DSDDescent(this.axis, DataSequenceClass.POSITION_SPACE,
                                        this.func == EdgeFunction.NonEmpty));
    } else {
      this.appendElement(new DSDAggregate(this.axis,
                                          DataSequenceClass.POSITION_SPACE,
                                          this.func));
    }
    this.axis = null;
    this.func = null;
    this.isAggregate = false;
    return this;
  }

  @Override
  public DSDFilterBuilder<T> run(final String name) {
    if (!this.isAggregate) {
      this.appendElement(new DSDDescent(this.axis, new RunClass(name),
                                        this.func == EdgeFunction.NonEmpty));
    } else {
      this.appendElement(new DSDAggregate(this.axis, new RunClass(name), this.func));
    }
    this.axis = null;
    this.func = null;
    return this;
  }

  public void setHead(final DSDElement head) {
    this.head = head;
  }
}
